import bs4
import re
from functools import wraps
from bs4 import PageElement

from utils.lg import logger
from domains.Question import Question, QueTypes

"""
耗费最多时间是在使用beautifulsoup4的各种方法中，因为之前没怎么用过(之前都是直接用lxml的xpath)
最搞心态的是 .children 中标签中的字符串也在里面，导致后续判断错误
自己对css的语法不是很熟练也费了很多时间(比如说如果标签之间用空格就会递归找所有子孙节点，之前没注意，习惯用空格出了很多错误，很多时候应该用'>')
还有就是lyr的html页面，answer标签名有的是div有的是span，找了好久才发现这个细节(也可以说是我眼瞎...)
"""


def log_when_get_ans(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logger.debug("开始解析答案")
        res = func(*args, **kwargs)
        logger.debug(f"获取到答案: {res.ans_list}")
        return res
    return wrapper


class QuestionParser:
    @logger.catch
    def __init__(self, html_file_path):
        logger.info(f"初始化 QuestionParser 并解析: {html_file_path}")
        self.html_file_path: str = html_file_path  # 答案回顾的html文件路径
        self.problem_href = None  # 答案回顾对应的答题链接
        self.que_count: int = 0  # 问题的个数
        self.que_list: list[Question] = self._get_que_list_from_html()  # 问题组装好答案后保存的列表
        self.desc2que: dict[str, Question] = {}  # 问题的描述到答案的映射，用于寻找对应答案
        self._get_desc2que()

    def _get_problem_href(self, soup):
        """获取答题的页面url"""
        self.problem_href = soup.find(class_="mod_quiz-next-nav")["href"]

    def _get_que_list_from_html(self) -> list[Question]:
        # 加载文件内容
        logger.debug("加载文件内容")
        with open(self.html_file_path, 'r', encoding='utf-8') as f:
            soup = bs4.BeautifulSoup(f.read(), 'lxml')
        # 获取答题的页面url
        self._get_problem_href(soup)
        logger.debug(f"获取到答题页面: {self.problem_href}")
        # 找到所有问题框
        logger.debug("寻找问题框")
        que_boxs = soup.find_all(id=re.compile(r'question'))
        # 统计问题个数
        self.que_count = len(que_boxs)
        logger.info(f"找到共 {self.que_count} 个问题")
        # 组装成问题列表
        que_list = []
        logger.info("开始处理问题")
        for i, que_box in enumerate(que_boxs):
            que_box: PageElement
            # 组装Question类对象
            logger.debug(f"正在处理问题{i + 1}")
            que_type = que_box['class'][1]
            logger.debug(f"获取到问题类型: {que_type}")
            # multianswer多输入简答题 和 gapselect多方框选择题 的问题和作答位置重合了，需要特殊处理
            # 使用contents[0]只取第一个位置的文本
            # .replace('\xa0', ' ') 去掉这个奇怪的特殊空格(不间断空白符 &nbsp;)
            if que_type == QueTypes.MUL_ANS or que_type == QueTypes.GAPS:
                desc = que_box.find_next(dir='ltr').contents[0].text.split('\n')[0].replace('\xa0', ' ')
            else:
                desc = que_box.find_next(class_='qtext').contents[0].text.split('\n')[0].replace('\xa0', ' ')
            que = Question(que_type, desc, [])
            # 完善ans列表
            # print(que.que_type)
            que = self._get_ans_content(que_box, que)
            que_list.append(que)
            logger.debug(f"问题{i + 1}处理完成")
        logger.info("全部问题处理完成")
        return que_list

    def _get_ans_content(self, que_box: PageElement, que: Question) -> Question:
        """
        根据问题类型和状态获取答案列表
        方法的整体逻辑都是先从问题标签que_box中找到answer标签，再从中找出选择的选项或填写的内容保存进que.ans_list
        :param que_box: 问题的整个标签栏
        :param que: 原始的问题对象
        :return: 完善了ans列表的问题对象
        """
        if que.que_type == QueTypes.GAPS:
            return self._get_ans_from_gaps(que_box, que)
        elif que.que_type == QueTypes.MUL_ANS:
            return self._get_ans_from_mul_ans(que_box, que)
        elif que.que_type == QueTypes.CHOSE:
            return self._get_ans_from_chose(que_box, que)
        elif que.que_type == QueTypes.SHORT_ANS:
            return self._get_ans_from_short_ans(que_box, que)
        elif que.que_type == QueTypes.JUDGE:
            return self._get_ans_from_judge(que_box, que)
        elif que.que_type == QueTypes.CHOSE_ETC:
            return self._get_ans_from_chose_etc(que_box, que)
        elif que.que_type == QueTypes.ESSAY:
            return self._get_ans_from_essay(que_box, que)

    @log_when_get_ans
    def _get_ans_from_essay(self, que_box: PageElement, que: Question) -> Question:
        lines = que_box.find_next(class_="answer").find_all(name="p", dir="ltr")
        text = ""
        for line in lines:
            text += line.text+"\n"
        que.ans_list.append(text)
        return que

    @log_when_get_ans
    def _get_ans_from_mul_ans(self, que_box: PageElement, que: Question) -> Question:
        spans = que_box.find_next(dir="ltr").find_all(name="span", class_="subquestion form-inline d-inline")
        for span in spans:
            que.ans_list.append(span.find_next(name="input", type="text")['value'])
        return que

    @log_when_get_ans
    def _get_ans_from_gaps(self, que_box: PageElement, que: Question) -> Question:
        spans = que_box.find_next(dir="ltr").find_all(name="span")
        for span in spans:
            que.ans_list.append(span.find_next(name="option", selected="selected").text)
        return que

    @log_when_get_ans
    def _get_ans_from_chose(self, que_box: PageElement, que: Question) -> Question:
        # answer标签名有的是div有的是span 非常坑
        answer_tag = que_box.find_next(name=["span", "div"], class_="answer")
        for choose in answer_tag.children:
            # 去除参杂在中间的字符串，这是由于beautifulsoup的返回里会把字符串塞在一起，个人不是很理解，在这里浪费了很多时间
            if isinstance(choose, str):
                continue
            # correct和incorrect和partiallycorrect都选，即完全按提供的答案回顾来，就不自己做题了(不如手工做对一份重新运行)
            if choose["class"][-1].endswith("correct"):
                que.ans_list.append(choose.find_next(class_="flex-fill ml-1").text)
        return que

    @log_when_get_ans
    def _get_ans_from_short_ans(self, que_box: PageElement, que: Question) -> Question:
        answer_tag = que_box.find_next(name=["span", "div"], class_="answer")
        que.ans_list.append(answer_tag.find_next(name="input", type="text")['value'])
        return que

    @log_when_get_ans
    def _get_ans_from_judge(self, que_box: PageElement, que: Question) -> Question:
        answer_tag = que_box.find_next(name=["span", "div"], class_="answer")
        for choose in answer_tag.children:
            if choose["class"][-1].endswith("correct"):
                que.ans_list.append(choose.find_next(name="label").text)
        return que

    @log_when_get_ans
    def _get_ans_from_chose_etc(self, que_box: PageElement, que: Question) -> Question:
        answer_tag = que_box.find_next(name=["span", "div"], class_="answer")
        for choose in answer_tag.children:
            if isinstance(choose, str):
                continue
            if choose["class"][-1].endswith("correct"):
                que.ans_list.append(choose.find_next(class_="flex-fill ml-1").text)
        return que

    def _get_desc2que(self):
        logger.info("获取问题题目到答案的映射: desc2que")
        for que in self.que_list:
            self.desc2que[que.desc] = que
        logger.info("desc2que 获取成功")


if __name__ == '__main__':
    qp = QuestionParser(
        r'D:\WeChatRubbish\WeChat Files\wxid_fj42g1evwc6p21\FileStorage\File\2024-04\2.3quiz：答题回顾(1).html')
    # r'D:\WeChatRubbish\WeChat Files\wxid_fj42g1evwc6p21\FileStorage\File\2024-04\2.3quiz：答题回顾(1).html'
    # print(qp.desc2que)
    # print(qp.problem_href)
    for de, q in qp.desc2que.items():
        print(f"desc: {de}")
        for i, li in enumerate(q.ans_list):
            print(f"ans_list[{i}]: {li}")
        print()
